package com.ironman.imes.device.data.transfer.listener;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ironman.imes.device.data.transfer.BuiltInSys;
import com.ironman.imes.device.data.transfer.model.Filter;
import com.ironman.imes.device.data.transfer.model.Rule;
import com.ironman.imes.device.data.transfer.model.Var;
import com.ironman.imes.device.event.EventTopic;
import com.ironman.imes.device.event.model.DeviceSourceData;
import com.ironman.imes.device.event.model.DeviceTransferData;
import com.ironman.imes.device.framework.base.threadpool.ThreadPool;
import com.ironman.imes.device.framework.orm.db1.TransferInfoMapper;
import com.ironman.imes.device.framework.orm.db1.dataobject.TransferInfo;
import com.ironman.imes.device.framework.orm.enums.EnableEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.noear.dami.Dami;
import org.noear.snack.ONode;
import org.noear.solon.expression.snel.SnEL;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

@Slf4j
@Component
@RequiredArgsConstructor
public class DeviceDataTransferAppReadyEventListener implements ApplicationListener<ApplicationReadyEvent> {

    private static final String TRANSFER_RULE_KEY = "{}_{}";

    private static final Map<String, Rule> TRANSFER_RULES = new HashMap<>();

    private static final Map<String, Object> CONTEXT = MapUtil.of("_sys", BuiltInSys.class);

    final TransferInfoMapper transferInfoMapper;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        List<TransferInfo> transferInfos = transferInfoMapper.selectList(Wrappers.lambdaQuery(TransferInfo.class)
                .select(TransferInfo::getDeviceType, TransferInfo::getDataSource, TransferInfo::getMap, TransferInfo::getFilter)
                .eq(TransferInfo::getEnable, EnableEnum.ENABLE));
        for (TransferInfo transferInfo : transferInfos) {
            String transferRuleKey = transferRuleKey(transferInfo.getDeviceType(), transferInfo.getDataSource());
            String map = transferInfo.getMap();
            String filter = transferInfo.getFilter();
            TRANSFER_RULES.put(transferRuleKey, Rule.builder()
                    .map(JSONUtil.toList(map, Var.class))
                    .filter(JSONUtil.toBean(filter, Filter.class))
                    .build());
        }

        Dami.<DeviceSourceData, Void>bus().listen(EventTopic.DEVICE_SOURCE_DATA_TOPIC, payload -> {
            DeviceSourceData sourceData = payload.getContent();
            String dataSource = sourceData.getDataSource();
            String deviceCode = sourceData.getDeviceCode();
            String deviceType = Dami.<String, String>bus().sendAndRequest(EventTopic.DEVICE_INFO_TOPIC, deviceCode);
            String data = sourceData.getData();
            if (StrUtil.isBlank(deviceType)) {
                log.warn("未知设备类型：deviceCode: {}", deviceCode);
                return;
            }
            sourceData.setDeviceType(deviceType);
            Rule rule = TRANSFER_RULES.get(transferRuleKey(deviceType, dataSource));
            if (Objects.isNull(rule)) {
                log.warn("尚未配置transfer_rule 【deviceType:{}, dataSource:{} deviceType: {}】", deviceType, dataSource, deviceCode);
                return;
            }
            log.info("deviceType: {}, dataSource: {}, deviceCode: {}, data: {}", deviceType, dataSource, deviceCode, data);
            ONode root = JSONUtil.isTypeJSON(data) ? ONode.loadStr(data) : Dami.<DeviceSourceData, ONode>bus().sendAndRequest(EventTopic.PROTOCOL_JA_EVAL_TOPIC, sourceData);
            List<Var> map = rule.getMap();
            Map<String, Object> params = map.stream().collect(Collectors.toMap(Var::getVarName, var -> {
                String varName = var.getVarName();
                String jpath = var.getJpath();
                ONode selected = root.select(jpath);
                Object val = toSpecificTypeVal(var.getType(), selected, var.getDefaultVal());
                String formatExpr = var.getFormatExpr();
                if (StrUtil.isNotBlank(formatExpr)) {
                    Map<String, Object> paramMap = new HashMap<>(CONTEXT);
                    paramMap.put(varName, val);
                    val = SnEL.eval(formatExpr, paramMap);
                }
                return val;
            }));
            log.info("before map: {}, after map: {}", root.toJson(), JSONUtil.toJsonStr(params));
            Filter filter = rule.getFilter();
            if (Objects.nonNull(filter)) {
                String expression = filter.getExpression();
                if (Boolean.FALSE.equals(SnEL.eval(expression, params))) {
                    log.info("过滤条件不满足：deviceCode: {}, expression: {} {}", deviceCode, expression, JSONUtil.toJsonStr(params));
                    return;
                }
            }

            Future<?> future = ThreadPool.SINK_FIXED_EXECUTOR.submit(() -> {
                Dami.<DeviceTransferData, Void>bus().send(EventTopic.DEVICE_TRANSFER_DATA_TOPIC, new DeviceTransferData(deviceType, dataSource, deviceCode, params));
            });
        });
    }

    private Object toSpecificTypeVal(String type, ONode selected, String defaultVal) {
        boolean isExpr = defaultVal.startsWith("expr:");
        if (isExpr) {
            defaultVal = defaultVal.split(":", 2)[1];
        }
        if ("string".equals(type)) {
            return selected.isNull() ? isExpr ? SnEL.eval(defaultVal, CONTEXT) : defaultVal : selected.getString();
        }
        if ("date".equals(type)) {
            return selected.isNull() ? (isExpr ? SnEL.eval(defaultVal, CONTEXT) : DateUtil.parse(defaultVal)) : selected.getDate();
        }
        if ("int".equals(type)) {
            return selected.isNull() ? isExpr ? SnEL.eval(defaultVal, CONTEXT) : Integer.parseInt(defaultVal) : selected.getInt();
        }
        if ("long".equals(type)) {
            return selected.isNull() ? isExpr ? SnEL.eval(defaultVal, CONTEXT) : Long.parseLong(defaultVal) : selected.getLong();
        }
        if ("float".equals(type)) {
            return selected.isNull() ? isExpr ? SnEL.eval(defaultVal, CONTEXT) : Float.parseFloat(defaultVal) : selected.getFloat();
        }
        if ("double".equals(type)) {
            return selected.isNull() ? isExpr ? SnEL.eval(defaultVal, CONTEXT) : Double.parseDouble(defaultVal) : selected.getDouble();
        }
        if ("bool".equals(type)) {
            return selected.isNull() ? isExpr ? SnEL.eval(defaultVal, CONTEXT) : Boolean.parseBoolean(defaultVal) : selected.getBoolean();
        }
        return isExpr ? SnEL.eval(defaultVal, CONTEXT) : selected.getString();
    }

    private String transferRuleKey(String deviceType, String dataSource) {
        return StrUtil.format(TRANSFER_RULE_KEY, deviceType, dataSource);
    }
}